package gin

import (
	"bytes"
	"encoding/json"
	"encoding/xml"
	"errors"
	"fmt"
	"gin_v0dot2b/binding"
	"github.com/julienschmidt/httprouter"
	"html/template"
	"log"
	"math"
	"net/http"
	"path"
	"sync"
)

const (
	AbortIndex = math.MaxInt8 / 2
	MIMEJSON   = "application/json"
	MIMEHtml   = "text/html"
	MIMEXml    = "application/xml"
	MIMEXML2   = "text/xml"
	MIMEPlain  = "text/plain"
)

// iota首次为0,1 << 0左移0位结果为1
// 第二次iota为1,1 << 1 左移1位结果为2
// 0xffffffff 标识一个32位的无符号证书
const (
	// ErrorTypeInternal 表示内部错误
	ErrorTypeInternal = 1 << iota
	// ErrorTypeExternal 表示外部错误
	ErrorTypeExternal = 1 << iota
	ErrorTypeAll      = 0xffffffff
)

type (
	// HandlerFunc 中间件函数规范(接收一个上下文对象)
	HandlerFunc func(*Context)

	// H 主要用于响应json
	H map[string]any

	// errorMsg 在内部用于收集http请求期间发生的错误
	errorMsg struct {
		Err  string `json:"error"`
		Type uint32 `json:"-"`
		Meta any    `json:"meta"`
	}
	errorMsgs []errorMsg

	Context struct {
		// 请求
		Req *http.Request
		// 响应
		Writer ResponseWriter
		// 用于存储上下文传递信息,如用户认证凭证等
		Keys map[string]any
		// 错误信息
		Errors errorMsgs
		// 动态路由参数
		Params httprouter.Params
		// 包含路由组的全局中间件以及用户传入的所有处理函数
		handlers []HandlerFunc
		// 通过路由组传递过来的引擎实例,也就是全局的那个唯一引擎
		Engine *Engine
		// 负责记录当前执行函数的索引(防止重复执行),同时用于控制处理函数的数量 128
		index int8
	}

	// RouterGroup 在内部用于配置路由器
	// 1.管理中间件列表
	// 2.借助httprouter实现动态路由
	RouterGroup struct {
		// 处理方法
		Handlers []HandlerFunc
		// 路由组前缀(主要用于路由分组)
		// 整个应用的路由前缀
		prefix string
		// 父级路由
		parent *RouterGroup
		engine *Engine
	}

	// Engine 代表web框架,包装了httprouter和全局中间件列表
	Engine struct {
		*RouterGroup
		HTMLTemplates *template.Template
		// sync.Pool用来缓存和复用对象
		// 当需要一个对象时可从sync.Pool中取,不使用时,将其放回Pool以便后续使用
		// 同时是并发安全的,可以在多个goroutine中并发使用
		// 通过Get()获取对象,通过Put()放回Pool
		cache sync.Pool
		// 处理404相关的中间件
		handlers404 []HandlerFunc
		// 初始化httprouter,创建httprouter实例对象
		router *httprouter.Router
	}
)

// MarshalXML 自定义H类型xml序列化过程
func (h H) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
	// 将当前元素的标签名设置为map,且不使用命名空间
	// xml中命名空间只要是为了防止同一文件中多个重复的标签
	// 一般是一个唯一标识符(网络地址或者其他标识符)
	start.Name = xml.Name{Space: "", Local: "map"}

	// 写入起始标签
	if err := e.EncodeToken(start); err != nil {
		return err
	}

	// 这里就是以key作为标签,value作为值
	for key, value := range h {
		// xml.StartElement的作用如下:
		// 1.定义xml元素的结构:包括元素名称,命名空间和属性
		// 2.作为参数传递给解码器,告诉解码器如何生成xml起始标签
		elem := xml.StartElement{
			Name: xml.Name{Space: "", Local: key},
			// xml标签的属性
			Attr: []xml.Attr{},
		}
		// e.EncodeElement(value, start)参数解释:
		// value: 要编码的go值(结构体,切片,基本类型等)
		// start: xml元素标签(包含元素名称和属性)
		// 创建xml节点并设置其值
		if err := e.EncodeElement(value, elem); err != nil {
			return err
		}
	}
	// 以map标签收尾
	if err := e.EncodeToken(xml.EndElement{Name: start.Name}); err != nil {
		return err
	}
	// 最终结构大概长这样:
	//<map>
	//	<key>
	//		value
	//	</key>
	//  <key>
	//		value
	//  </key>
	// ...
	//</map>
	return nil
}

func (a errorMsgs) String() string {
	// bytes.Buffer本质上是一个动态增长的字节数组
	// 可以方便的进行字节的写入,读取,追加等操作
	var buffer bytes.Buffer
	// 依次将错误信息追加到bytes.Buffer末尾
	for i, msg := range a {
		text := fmt.Sprintf("Error #%02d: %s \n   Meta: %v\n", i+i, msg.Err, msg.Meta)
		// WriteString 将字符串追加到bytes.Buffer末尾
		buffer.WriteString(text)
	}
	// 将bytes.Buffer转换为字符串并返回
	return buffer.String()
}

// New 返回一个没有附加任何中间件的空白Engine实例
func New() *Engine {
	engine := &Engine{}
	engine.RouterGroup = &RouterGroup{nil, "", nil, engine}
	engine.router = httprouter.New()
	engine.router.NotFound = http.HandlerFunc(engine.handle404)
	// 指定sync.Pool创建对象逻辑
	engine.cache.New = func() any {
		return &Context{Engine: engine, Writer: &responseWriter{}}
	}
	return engine
}

// Default 返回一个已使用Recovery和Logger的引擎实例
func Default() *Engine {
	engine := New()
	engine.Use(Recovery(), Logger())
	return engine
}

// LoadHTMLTemplates 初始化HTML模板
func (engine *Engine) LoadHTMLTemplates(pattern string) {
	//ParseGlob根据指定的模式来匹配解析多个模板文件.支持*,?等,如templates/*.tmpl
	//Must 是一个辅助函数，主要用于根据err是否为空来快速触发恐慌中断程序
	// 当未使用{{define}}定义模板名称时,模板名称默认为文件名(去除路径和扩展名后的文件名)
	engine.HTMLTemplates = template.Must(template.ParseGlob(pattern))
}

// NotFound404 添加处理404的方法,默认返回404code
func (engine *Engine) NotFound404(handlers ...HandlerFunc) {
	engine.handlers404 = handlers
}

// handle404 404处理是一个单独的上下文
func (engine *Engine) handle404(w http.ResponseWriter, req *http.Request) {
	// 将用户自定义的404中间件添加至洋葱链中
	handlers := engine.combineHandlers(engine.handlers404)
	// 创建一个新的上下文对象
	c := engine.createContext(w, req, nil, handlers)
	// 如果没有404处理逻辑则用默认的http404处理
	if engine.handlers404 == nil {
		http.NotFound(c.Writer, c.Req)
	} else {
		// 否则使用自己的
		c.Writer.WriteHeader(404)
	}
	// 控制权转让出去,执行其他中间件
	c.Next()
}

// Use 向中间件列表中追加一个中间件函数
func (group *RouterGroup) Use(middlewares ...HandlerFunc) {
	group.Handlers = append(group.Handlers, middlewares...)
}

func (engine *Engine) Run(addr string) {
	if err := http.ListenAndServe(addr, engine); err != nil {
		panic(err)
	}
}

// ServeHTTP 使路由器实现http.Handler接口
func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	// 借助httprouter.ServeHTTP处理请求路径,匹配路由,处理路由参数等
	// 也就是这一步替换了原生http包的路由处理方式
	engine.router.ServeHTTP(w, req)
}

/************************************/
/********** ROUTES GROUPING *********/
/************************************/

func (engine *Engine) createContext(w http.ResponseWriter, req *http.Request, params httprouter.Params, handlers []HandlerFunc) *Context {
	// 从Pool中获取一个对象(首次调用不存在会用内部New创建一个),并尝试转换为Context
	// 只是减少对象创建的频率,减少内存分配次数(上下文的值每次都需要重新设置)
	c := engine.cache.Get().(*Context)
	c.Writer.reset(w)
	c.Req = req
	c.Params = params
	c.handlers = handlers
	c.Keys = nil
	c.index = -1
	return c
}

func (group *RouterGroup) Group(component string, handlers ...HandlerFunc) *RouterGroup {
	// 指定组的路由前缀
	prefix := path.Join(group.prefix, component)
	return &RouterGroup{
		// 这里调用combineHandlers将路由组的中间件组合起来，以达到针对于不同的路由组的中间件
		Handlers: group.combineHandlers(handlers),
		parent:   group,
		prefix:   prefix,
		engine:   group.engine,
	}
}

// Handle 使用给定的路径和方法注册一个新的请求句柄和中间件
// 最后一个处理程序应该是真正的处理程序,其他的应该是可以并且能在不同路由之间共享的中间件
// 此函数旨在批量加载并允许使用更少的常用的,非标准化的或自定义的方法
// Handle方法一结束里面的handlers就会清空(除了全局中间件)
func (group *RouterGroup) Handle(method, p string, handlers []HandlerFunc) {
	p = path.Join(group.prefix, p)
	//fmt.Println("前缀:", group.prefix)
	//fmt.Println("路径:", p)

	// 将路由组的全局中间件和用户传入的处理函数合并成一个函数切片,按顺序合并,全局中间件在前,用户处理函数在后
	handlers = group.combineHandlers(handlers)

	// 借助httprouter的路由处理函数来处理不同请求方式的请求,并且支持动态路由
	group.engine.router.Handle(method, p, func(w http.ResponseWriter, req *http.Request, params httprouter.Params) {
		// 创建一个新的上下文对象,并通过Next依次调用中间件函数和句柄函数,当前版本同时最多支持128个处理函数
		c := group.engine.createContext(w, req, params, handlers)
		c.Next()
		// 处理完后,将对象放回Pool
		group.engine.cache.Put(c)
	})
}

// GET 路由器的快捷方式. Handle（"GET"， path，句柄）
func (group *RouterGroup) GET(path string, handlers ...HandlerFunc) {
	group.Handle("GET", path, handlers)
}

// POST 路由器的快捷方式. Handle（"POST"， path，句柄）
// POST请求不具有幂等性，多次相同的post请求会创建多个相同的资源
func (group *RouterGroup) POST(path string, handlers ...HandlerFunc) {
	group.Handle("POST", path, handlers)
}

// DELETE 路由器的快捷方式. Handle（"DELETE"， path，句柄）
// 通常具有幂等性
func (group *RouterGroup) DELETE(path string, handlers ...HandlerFunc) {
	group.Handle("DELETE", path, handlers)
}

// PATCH 路由器的快捷方式. Handle（"PATCH"， path，句柄）
// 对服务器的资源进行部分更新,与put不同的是,put通常是替换整个资源
// 不具备幂等性,每次更新可能会产生不同的结果,比如只更新用户的电子邮箱
func (group *RouterGroup) PATCH(path string, handlers ...HandlerFunc) {
	group.Handle("PATCH", path, handlers)
}

// PUT 路由器的快捷方式. Handle（"PUT"， path，句柄）
// put请求常用于更新服务器上的资源,通常具有幂等性,多次执行相同的put所产生的效果是相同的
// 比如更新整个用户的信息
func (group *RouterGroup) PUT(path string, handlers ...HandlerFunc) {
	group.Handle("PUT", path, handlers)
}

// Static 用于从执行的文件系统根目录提供文件服务
// 内部使用http.FileServer,因此使用http.NotFind代理路由器的NotFind处理程序
func (group *RouterGroup) Static(p, root string) {
	// 这里使用了动态路由,p路径后面的值会被当作filepath参数处理
	// *表示匹配任意数量的任意字符
	p = path.Join(p, "/*filepath")
	// http.FileServer 用于提供文件系统中的静态文件服务
	// 能够将指定的文件系统目录映射为一个HTTP服务
	// http.Dir将本地文件系统的一个目录转换为http.FileSystem接口类型的值
	fileServer := http.FileServer(http.Dir(root))

	group.GET(p, func(c *Context) {
		// 保存原始url路径
		original := c.Req.URL.Path
		// 修改请求的url路径(也就是拿到参数p后面字符串,以此修改请求路径,这样FileServer()就会从指定的路径中查找文件)
		c.Req.URL.Path = c.Params.ByName("filepath")
		// 执行文件服务
		fileServer.ServeHTTP(c.Writer, c.Req)
		// 恢复原始url路径
		c.Req.URL.Path = original
	})
}

// combineHandlers 将中间件函数和句柄函数切片合并为一个处理函数切片
func (group *RouterGroup) combineHandlers(handlers []HandlerFunc) []HandlerFunc {
	// 根据中间件的函数以及真正要执行的程序函数创建一个合适容量的处理函数切片
	s := len(group.Handlers) + len(handlers)
	// 创建一个s容量的切片
	h := make([]HandlerFunc, 0, s)
	// 添加中间件函数
	h = append(h, group.Handlers...)
	// 添加句柄函数
	h = append(h, handlers...)
	// 返回处理函数切片
	return h
}

/************************************/
/****** FLOW AND ERROR MANAGEMENT****/
/************************************/

// Next 应该只在中间件中使用
// 它执行调用处理程序内部链中的挂起处理程序
// 简单来讲就是循环执行所有中间件切片
func (c *Context) Next() {
	// 记录当前正在执行的处理函数的索引,最开始为-1
	// 顺便起到控制的作用,以防止重复执行
	// 因为index属于Context,因此只要Context不是新的,那么index就会持续保持计数
	c.index++
	// 控制处理函数的数量,最多128个,程序不会出错,但会执行若干个函数(由转换后的数字决定,但不会超过127,从0开始)
	s := int8(len(c.handlers))
	fmt.Println("当前上下文总处理函数数量:", s)
	// 循环依次处理,最多处理128个处理函数,每次都将上下文传递给下一个处理函数
	// 每循环一次index+1,意味着执行过了一个处理函数
	// 下次处理是就可以接着上一次index的位置继续处理
	for ; c.index < s; c.index++ {
		fmt.Println("正在依次执行处理函数(包括中间件和处理函数()):", c.index)
		c.handlers[c.index](c)
	}
}

// Abort 强制系统不要继续调用挂起的处理程序
// 如:第一个处理程序检查请求是否被授权,如果不是,则应调用context.Abort(401)
// 永远不会为该请求调用其余的挂起处理程序
func (c *Context) Abort(code int) {
	c.Writer.WriteHeader(code)
	// 直接修改index为63,这样如果总处理函数没有超过63,那么则不会执行其他挂起函数
	c.index = AbortIndex
}

// Fail 停止处理并返回错误
func (c *Context) Fail(code int, err error) {
	// 添加操作终端错误信息到错误列表
	c.Error(err, "Operation aborted")
	c.Abort(code)
}

// ErrorTyped 添加错误并设置错误类型
func (c *Context) ErrorTyped(err error, typ uint32, meta any) {
	c.Errors = append(c.Errors, errorMsg{
		Err:  err.Error(),
		Type: typ,
		Meta: meta,
	})
}

// Error 将错误附加到当前上下文。错误被推送到错误列表中。
// 最好为解决请求期间发生的每个错误调用Error。
// 中间件可用于收集所有错误并将它们一起推送到数据库、打印日志或附加到HTTP响应中
func (c *Context) Error(err error, meta any) {
	c.ErrorTyped(err, ErrorTypeExternal, meta)
}

/************************************/
/******** METADATA MANAGEMENT********/
/************************************/

// Set 为指定的上下文设置一个新的键值对
// 延迟初始化hashmap(懒加载)
func (c *Context) Set(key string, item any) {
	if c.Keys == nil {
		c.Keys = make(map[string]any)
	}
	c.Keys[key] = item
}

// Get 返回给定键的值(容忍值不存在的情况)
func (c *Context) Get(key string) (any, error) {
	if c.Keys != nil {
		item, ok := c.Keys[key]
		if ok {
			return item, nil
		}
	}
	return nil, errors.New("key does not exist")
}

// MustGet 相较于Get(),则不存在nil的情况,得不到则触发恐慌
func (c *Context) MustGet(key string) any {
	value, err := c.Get(key)
	if err != nil || value == nil {
		log.Panicf("key %s doesn't exist", key)
	}
	return value
}

/************************************/
/******** ENCOGING MANAGEMENT********/
/************************************/

// filterFlags 只要;前面的部分(提取首个空格或;之前的部分)
// 不存在这些符号则返回完整字符串
func filterFlags(content string) string {
	for i, a := range content {
		if a == ' ' || a == ';' {
			return content[:i]
		}
	}
	return content
}

// Bind 根据请求中的Content-Type来选择不同的类型解析器
// 这些解析器主要为了完成数据类型映射
// 目前支持表单,json,xml,其他类型暂不支持
// 如果结构体使用了特殊标签(如某个必须字段),校验不通过也会返回false
func (c *Context) Bind(obj any) bool {
	var b binding.Binding
	ctype := filterFlags(c.Req.Header.Get("Content-Type"))
	switch {
	// 如果是get请求,则使用表单
	case c.Req.Method == "GET":
		b = binding.Form
	// 如果是json,则用json解析处理
	case ctype == MIMEJSON:
		b = binding.JSON
	case ctype == MIMEXml || ctype == MIMEXML2:
		b = binding.XML
	//	如果以上格式都不是,则返回错误
	default:
		c.Fail(400, errors.New("unknown content-type: "+ctype))
		return false
	}
	return c.BindWith(obj, b)
}

func (c *Context) BindWith(obj any, b binding.Binding) bool {
	if err := b.Bind(c.Req, obj); err != nil {
		c.Fail(400, err)
		return false
	}
	return true
}

// String 现在支持带格式的字符串响应
// TODO 目前存在编码问题,不支持中文,或者需要研究一下format
func (c *Context) String(code int, format string, values ...any) {
	c.Writer.Header().Set("Content-Type", MIMEPlain)
	if code >= 0 {
		c.Writer.WriteHeader(code)
	}
	// format用于执行输出的格式,values代表要进行格式化的值
	// 如fmt.Sprintf("%s's score is %.2f", name, score)
	c.Writer.Write([]byte(fmt.Sprintf(format, values)))
}

// JSON 以json格式响应客户端
func (c *Context) JSON(code int, obj any) {
	c.Writer.Header().Set("Content-Type", MIMEJSON)
	if code >= 0 {
		c.Writer.WriteHeader(code)
	}
	// 将go的数据结构编码为JSON格式，并将编码后的JSON数据写入c.Writer到输出中
	encoder := json.NewEncoder(c.Writer)

	// 将obj编译Json串
	if err := encoder.Encode(obj); err != nil {
		// 如果出现错误,将错误信息添加到错误列表中
		c.ErrorTyped(err, ErrorTypeInternal, obj)
		c.Abort(500)
	}
}

// XML 将特定结构体作为XML序列化到响应正文中
//
//	结构体如: type User struct {
//			XMLName xml.Name `xml:"user"`
//	 	Name string `xml:"name"`
//	     Age uint8  `xml:"age"`
//		}
func (c *Context) XML(code int, obj any) {

	c.Writer.Header().Set("Content-Type", MIMEXml)
	if code >= 0 {
		c.Writer.WriteHeader(code)
	}
	// 创建一个XML编码器,编码后的xml数据会被写入到c.Writer中
	encoder := xml.NewEncoder(c.Writer)
	// 将obj编译为XML串,写入到c.Writer中
	if err := encoder.Encode(obj); err != nil {
		c.ErrorTyped(err, ErrorTypeInternal, obj)
		c.Abort(500)
	}
}

// HTML 响应由name指定的http模板
// 更多的见http://golang.org/doc/articles/wiki/
func (c *Context) HTML(code int, name string, data any) {
	c.Writer.Header().Set("Content-Type", MIMEHtml)

	if code >= 0 {
		c.Writer.WriteHeader(code)
	}
	// c.Writer渲染后模板内容会被写入到这里
	// name 要执行的模板名称，在模板集合中每个模板都有一个唯一的名字(如未指定{{define}}则默认为文件名)
	// data 传递给模板的数据
	if err := c.Engine.HTMLTemplates.ExecuteTemplate(c.Writer, name, data); err != nil {
		// 出现了错误,收集错误,并中断后续处理
		c.ErrorTyped(err, ErrorTypeInternal, H{
			"name": name,
			"data": data,
		})
		c.Abort(500)
	}
}

// Data 将一些数据写入正文流并更新HTTP状态码
// 现在可以自行指定响应内容的内容
func (c *Context) Data(code int, contentType string, data []byte) {
	if len(contentType) > 0 {
		c.Writer.Header().Set("Content-Type", contentType)
	}
	if code >= 0 {
		c.Writer.WriteHeader(code)
	}
	c.Writer.Write(data)
}
